Skip to content

feat: React UI v2.0 — Phase 0 backend foundation + Phase 1 scaffold start#17

Merged
aksOps merged 15 commits into
mainfrom
worktree-phase-0-backend
May 16, 2026
Merged

feat: React UI v2.0 — Phase 0 backend foundation + Phase 1 scaffold start#17
aksOps merged 15 commits into
mainfrom
worktree-phase-0-backend

Conversation

@aksOps

@aksOps aksOps commented May 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase 0 (backend foundation) and the start of Phase 1 (frontend scaffold) for the React UI v2.0 milestone. Spec + plan are gitignored agent artifacts (preserved in \$CLAUDE_JOB_DIR/preserved/).

Spec: `docs/superpowers/specs/2026-05-15-react-ui-design.md`
Plan: `docs/superpowers/plans/2026-05-15-react-ui-v2.md`

What's in this PR

Phase 0 — Backend foundation (10 task commits, all reviewed)

Task What Commit
1 Move API under `/api/v1/*` router `30bf5af`
2 308 redirects from legacy `/incidents/` + `/investigate` paths `2223985`
3 `GET /api/v1/sessions/{id}/full` bootstrap endpoint `ea1da02` + `9d310ef`
4 `GET /api/v1/config/ui-hints` + `UIConfig` schema `d7fd39c`
5 `GET /api/v1/apps/{app}/ui-views` + `AppView` schema `30d79d5`
6 Cross-session SSE `GET /api/v1/sessions/recent/events` `2fe578d`
7 StaticFiles mount at `/` with SPA fallback `16fbb24`
8 Env-driven CORS origins (default: Vite dev ports) `7d2e758`
9 Sweep-update existing tests for `/api/v1/*` URL surface `863e497`
10 Bundler fix: rename sidecar functions to unique names so the flatten works `22aeca6`

Phase 1 — Frontend scaffold (3 commits — start)

Task What Commit
11 `web/` directory with Vite + React 19 + TypeScript `c43b11c` (+ `d442e95` typecheck-script fix)
12 Tailwind v4 + design-tokens CSS preamble (single-source-of-truth tokens) `ad630dd`

Quality gates (Phase 0)

  • ✅ `uv run pytest --cov=src/runtime --cov-fail-under=85 -x` — 1334 passed / 0 failed / 8 skipped, 88.75% coverage
  • ✅ `uv run ruff check src/ tests/` — clean
  • ✅ `uv run pyright src/runtime` — 0 errors / 0 warnings
  • ✅ Concept-leak ratchet: 39 (matches baseline)
  • ✅ Skill-prompt linter: OK
  • ✅ `dist/` regenerated and committed per-commit (HARD-08 gate satisfied)

Quality gates (Phase 1 partial)

  • ✅ `cd web && npm install` — 362 packages, 0 vulnerabilities
  • ✅ `cd web && npm run build` — 60.8 kB JS gzip + 2.0 kB CSS gzip
  • ✅ `cd web && npm run typecheck` — clean (TypeScript strict mode)
  • ✅ Dev server boots, serves the React placeholder with token-driven styling

Remaining work

Phase 1 (Tasks 13-20): vendor fonts · TS tokens · Vitest · Icon · Button · Pill · Input · Modal
Phase 2 (Tasks 21-31): API client, SSE/WS hooks, sessionReducer, all data hooks, selectedRef
Phase 3 (Tasks 32-36): Topbar · FlowStrip · SessionsRail · Statusbar
Phase 4 (Tasks 37-42): SessionCanvas · Transcript · Turn · HITLBand
Phase 5 (Tasks 43-50): MonitorRail + 6 panels
Phase 6 (Tasks 51-56): Modals + 3 E2E flows
Phase 7 (Tasks 57-61): Responsive (tablet + mobile)
Phase 8 (Tasks 62-69): Build / deploy / CI / docs / tag
Phase 9 (Tasks 70-71): Streamlit sunset

Known latent issue (NOT introduced by this PR)

`dist/app.py` cannot complete `uvicorn --factory` startup end-to-end due to a pre-existing bundler bug in `Orchestrator.create`: the function-local `from runtime.config import VectorConfig as _VectorConfig` (orchestrator.py:514) is stripped by the bundler but the call site references `_VectorConfig` → NameError. This same bug exists on `main` (`dist/app.py:14021`). The 1334-test suite uses `build_app(cfg)` directly through Python imports (no bundle involved) and is unaffected. Track for later cleanup.

Test plan

  • Phase 0: 1334 tests passing locally with 88.75% coverage
  • Phase 1 scaffold: `npm run build` + `npm run typecheck` both clean
  • CI green on PR (will run on push)

🤖 Generated with Claude Code

Comment thread dist/app.py
include_in_schema=False,
)
async def _legacy_incidents_detail(path: str) -> RedirectResponse:
return RedirectResponse(url=f"/api/v1/sessions/{path}", status_code=308)
Comment thread dist/app.py
)
async def _legacy_investigate_subpath(path: str) -> RedirectResponse:
return RedirectResponse(
url=f"/api/v1/investigate/{path}", status_code=308,
Comment thread dist/apps/code-review.py
include_in_schema=False,
)
async def _legacy_incidents_detail(path: str) -> RedirectResponse:
return RedirectResponse(url=f"/api/v1/sessions/{path}", status_code=308)
Comment thread dist/apps/code-review.py
)
async def _legacy_investigate_subpath(path: str) -> RedirectResponse:
return RedirectResponse(
url=f"/api/v1/investigate/{path}", status_code=308,
include_in_schema=False,
)
async def _legacy_incidents_detail(path: str) -> RedirectResponse:
return RedirectResponse(url=f"/api/v1/sessions/{path}", status_code=308)
)
async def _legacy_investigate_subpath(path: str) -> RedirectResponse:
return RedirectResponse(
url=f"/api/v1/investigate/{path}", status_code=308,
Comment thread src/runtime/api.py
include_in_schema=False,
)
async def _legacy_incidents_detail(path: str) -> RedirectResponse:
return RedirectResponse(url=f"/api/v1/sessions/{path}", status_code=308)
Comment thread src/runtime/api.py
)
async def _legacy_investigate_subpath(path: str) -> RedirectResponse:
return RedirectResponse(
url=f"/api/v1/investigate/{path}", status_code=308,
@aksOps aksOps changed the title feat(api): React UI v2.0 — Phase 0 backend foundation feat: React UI v2.0 — Phase 0 backend foundation + Phase 1 scaffold start May 16, 2026
aksOps and others added 15 commits May 16, 2026 02:00
Code-review follow-ups for Phase 0 Task 3 (PR for /api/v1/sessions/
{id}/full bootstrap endpoint):

- Move the ``from runtime import api_session_full`` import from inside
  ``build_app`` to module-level alongside the other ``from runtime.…``
  imports — restores consistency with the rest of api.py.
- Add a docstring note in api_session_full.py distinguishing it from
  api_dedup.py: this module requires ``app.state.orchestrator`` so it
  cannot be used with bare ``FastAPI()`` test fixtures — call
  ``build_app(cfg)`` instead.
- Tighten ``list[dict]`` / ``dict[str, dict]`` annotations to
  ``list[dict[str, Any]]`` / ``dict[str, dict[str, Any]]``.

dist/* regenerated per Phase 0 norm.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drives the React UI's brand block, env switcher list, and
approval-rationale dropdown. Read once at React-app boot and
cached for the session lifetime via useUiHints().

- Extend UIConfig with brand_name, brand_logo_url,
  approval_rationale_templates, hitl_question_templates (all
  optional with empty defaults; existing badges/detail_fields/
  tags fields and the frozen+forbid model_config are unchanged).
- New api_ui_hints sidecar mirrors the api_session_full pattern
  (module-level import + add_routes(api_v1)).
- Surface AppConfig on app.state.cfg in the lifespan so the
  endpoint can read ui + environments without re-loading YAML.
- Bundle api_ui_hints.py after api_session_full.py.
- Regen dist/* per the Phase 0 per-commit dist-regen norm.
Phase 0 / Task 5. Approach C extensibility: apps register bespoke UI
overlay views via cfg.ui.app_views; the framework UI's Selected-detail
panel discovers them and renders "App-specific views ->" links.

v2.0 ships one app per deployment, so the path's {app} segment is
informational only — multi-app filtering is v2.1 scope.

Sidecar module mirrors the api_session_full / api_ui_hints pattern.
Adds runtime/api_static.py side-car: mounts /assets/* and /fonts/*
from $ASR_WEB_DIST (default web/dist) and a catch-all
GET /{full_path:path} that returns index.html so the React Router
can pick up arbitrary URLs. /api/, /health, /docs, /openapi.json
are reserved and return a structured JSON 404 envelope so unknown
API paths are not shadowed by the SPA. When the bundle isn't built
yet, GET / returns a 503 with a "cd web && npm ci && rtk npm run build"
hint to help dev users.

build_app calls api_static.mount(fastapi_app) AFTER include_router
+ legacy redirects so the catch-all is the last route registered.
Bundler picks up api_static.py after api_recent_events.py.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…le flattens cleanly

Phase 0 introduced five new side-car routing modules (api_session_full,
api_ui_hints, api_apps_overlay, api_recent_events, api_static) that each
exposed an ``add_routes(api_v1)`` (or ``mount(app)``) entry point and
were called from ``runtime.api.build_app`` via the module-attribute
form ``api_<name>.add_routes(api_v1)``.

That works in source where the modules are imported, but breaks in the
single-file bundle:

1. The bundler strips ``from runtime import (api_*)`` lines without
   creating module-namespace shims, so ``api_session_full`` is not a
   defined name at runtime → ``NameError``.
2. The four ``def add_routes`` defs collide at module scope when
   flattened — only the last one wins, so even with shims the wrong
   handler would be called.

Fix: rename to unique, module-qualified function names and import them
directly:

  api_session_full.add_routes  → add_session_full_routes
  api_ui_hints.add_routes      → add_ui_hints_routes
  api_apps_overlay.add_routes  → add_apps_overlay_routes
  api_recent_events.add_routes → add_recent_events_routes
  api_static.mount             → mount_static_assets

In source, ``api.py`` now does ``from runtime.api_session_full import
add_session_full_routes`` and calls the bare function name. In the
bundle, the strip pass removes the import line and the bare call
resolves to the unique flattened def. No collisions, no module-shim
gymnastics.

Verified end-to-end via TestClient against ``dist/app.py``:
  - GET /health → 200
  - GET /api/v1/config/ui-hints → 200 with full UIConfig payload
  - GET /api/v1/apps/incident_management/ui-views → 200 []
  - GET /incidents → 308 → /api/v1/sessions
  - POST /investigate → 308 → /api/v1/investigate

All 1334 tests pass; coverage 88.75%; ruff/pyright/concept-leak/
skill-lint all green; ``dist/*`` regenerated.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
First task of Phase 1 (React UI v2.0). Scaffolds web/ at the repo root
with pinned production-grade dependencies, strict TypeScript, and a
minimal placeholder App that proves the build + dev-server work end to
end.

Why this shape:
- Vite 7 + React 19 + TS 5.9 in strict mode (noUncheckedIndexedAccess,
  exactOptionalPropertyTypes) — the bar for the v2 UI is "no `any`,
  no fudged types".
- Dev-server proxy /api -> http://localhost:8000 so we can hit the
  Phase-0 FastAPI backend from React without CORS in dev.
- Top-level .gitignore updated so web/node_modules, web/dist,
  web/playwright-report, web/test-results never reach the index.

Verification:
- npm install: 362 packages, 0 vulnerabilities (npm 11.12.1, node 24.15.0).
- npm run build: tsc -b clean, vite build emits dist/index.html (331 B)
  + dist/assets/index-*.js (193 kB / 60.8 kB gzip) + sourcemap.
- npm run dev: serves the placeholder at http://localhost:5173/ with
  the expected #root mount-point and "ASR Operator Console" title.

Note: @types/node added to support process.env.npm_package_version in
vite.config.ts; types pulled in via tsconfig.node.json `types: ["node"]`
(scoped to the build-config layer, not the app code).

Subsequent tasks (12-20) layer in design tokens, Tailwind v4 config,
shadcn-style components, query client, generated API types, etc.
`tsc -b --noEmit` errors with TS6310 because the referenced composite
project tsconfig.node.json must emit `.d.ts`/`.tsbuildinfo` to satisfy
project-references. The app-level tsconfig.json already sets
`noEmit: true`, so the build script (`tsc -b && vite build`) and the
typecheck script (`tsc -b`) both correctly type-check src/ without
producing JS for the app.

Verified: `npm run typecheck` exits clean.
@aksOps aksOps force-pushed the worktree-phase-0-backend branch from ad630dd to d730533 Compare May 16, 2026 02:10
@socket-security

Copy link
Copy Markdown

@socket-security

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm entities is 91.0% likely obfuscated

Confidence: 0.91

Location: Package overview

From: web/package-lock.jsonnpm/jsdom@25.0.1npm/entities@6.0.1

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/entities@6.0.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@sonarqubecloud

sonarqubecloud Bot commented May 16, 2026

Copy link
Copy Markdown

@aksOps aksOps merged commit d7227c2 into main May 16, 2026
8 checks passed
@aksOps aksOps deleted the worktree-phase-0-backend branch May 16, 2026 10:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants